package com.jstarcraft.ai.jsat.math.decayrates;

import com.jstarcraft.ai.jsat.parameters.Parameterized;

/**
 * The Linear Decay requires the maximum time step to be explicitly known ahead
 * of time. Provided either in the call to
 * {@link #rate(double, double, double) }, or internal by
 * {@link #setMinRate(double) }. <br>
 * <br>
 * The Linear Decay will decay at a constant rate / slope from the initial value
 * until the specified {@link #setMinRate(double) } is reached.
 * 
 * @author Edward Raff
 */
public class LinearDecay implements DecayRate, Parameterized {

    private static final long serialVersionUID = 4934146018742844875L;
    private double min;
    private double maxTime;

    /**
     * Creates a new linear decay that goes down to 1e-4 after 100,000 units of time
     */
    public LinearDecay() {
        this(1e-4, 100000);
    }

    /**
     * Creates a new Linear Decay <br>
     * <br>
     * Note that when using {@link #rate(double, double, double) }, the maxTime is
     * always superceded by the value given to the function.
     * 
     * @param min     a value less than the learning rate that, that will be the
     *                minimum returned value
     * @param maxTime the maximum amount of time
     */
    public LinearDecay(double min, double maxTime) {
        setMinRate(min);
        setMaxTime(maxTime);
    }

    /**
     * Sets the minimum learning rate to return
     * 
     * @param min the minimum learning rate to return
     */
    public void setMinRate(double min) {
        if (min <= 0 || Double.isNaN(min) || Double.isInfinite(min))
            throw new RuntimeException("minRate should be positive, not " + min);
        this.min = min;
    }

    /**
     * Returns the minimum value to return from he <i>rate</i> methods
     * 
     * @return the minimum value to return
     */
    public double getMinRate() {
        return min;
    }

    /**
     * Sets the maximum amount of time to allow in the rate decay. Any time value
     * larger will be treated as the set maximum.<br>
     * <br>
     * Any calls to {@link #rate(double, double, double) } will use the value
     * provided in that method call instead.
     * 
     * @param maxTime the maximum amount of time to allow
     */
    public void setMaxTime(double maxTime) {
        if (maxTime <= 0 || Double.isInfinite(maxTime) || Double.isNaN(maxTime))
            throw new RuntimeException("maxTime should be positive, not " + maxTime);
        this.maxTime = maxTime;
    }

    /**
     * Returns the maximum time to use in the rate decay
     * 
     * @return the maximum time to use in the rate decay
     */
    public double getMaxTime() {
        return maxTime;
    }

    @Override
    public double rate(double time, double maxTime, double initial) {
        if (time < 0)
            throw new ArithmeticException("Negative time value given");
        return (initial - min) * (1.0 - Math.min(time, maxTime) / maxTime) + min;
    }

    @Override
    public double rate(double time, double initial) {
        return rate(time, maxTime, initial);
    }

    @Override
    public DecayRate clone() {
        return new LinearDecay(min, maxTime);
    }

    @Override
    public String toString() {
        return "Linear Decay";
    }
}
